/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "platform_event_runner.h"

#include <dlfcn.h>
#include <functional>
#include <memory>
#include <unistd.h>
#include <unordered_set>
#include <uv.h>

#include "event_handler.h"
#include "event_queue.h"
#include "event_runner.h"
#include "hilog.h"
#include "io_waiter.h"

std::thread::id g_mainThreadId;
uv_loop_s* g_loop;

namespace Keels {
namespace AppExecFwk {
class PlatformIoWaiter final : public IoWaiter {
public:
    using TimerCallback = std::function<void()>;
    PlatformIoWaiter()
    {
        loop_ = g_loop;
        if (loop_ == nullptr) {
            HILOG_ERROR("loop_ is nullptr, please set it first.");
        }
        async_ = new uv_async_t;
        uv_async_init(loop_, async_, AsyncCallback);
        async_->data = this;
    }

    ~PlatformIoWaiter() final
    {
        SetAsyncCallbackParam(true, false, true, 0);
        uv_async_send(async_);
    }

    bool StartTimer(const TimerCallback& callback)
    {
        SetAsyncCallbackParam(false, true, false, 0);
        uv_async_send(async_);

        timerCallback_ = callback;
        return true;
    }

    void StopTimer()
    {
        SetAsyncCallbackParam(false, false, true, 0);
        uv_async_send(async_);
    }

    void SetTimer(int64_t nanoseconds)
    {
        SetAsyncCallbackParam(false, true, false, nanoseconds);
        uv_async_send(async_);
    }

    bool WaitFor(std::unique_lock<std::mutex>& lock, int64_t nanoseconds) final
    {
        SetAsyncCallbackParam(false, true, false, nanoseconds);
        uv_async_send(async_);
        lock.unlock();
        uv_run(loop_, UV_RUN_DEFAULT);
        lock.lock();
        return true;
    }

    void NotifyOne() final
    {
        NotifyAll();
    }

    void NotifyAll() final
    {
        SetAsyncCallbackParam(false, true, false, 0);
        uv_async_send(async_);
    }

    bool SupportListeningFileDescriptor() const final
    {
        return true;
    }

    bool AddFileDescriptor(int32_t fd, uint32_t events) final
    {
        HILOG_ERROR("Not implemented, It will be added later.");
        return true;
    }

    void RemoveFileDescriptor(int32_t fileDescriptor) final
    {
        HILOG_ERROR("Not implemented, It will be added later.");
    }

    void SetFileDescriptorEventCallback(const FileDescriptorEventCallback& callback) final
    {
        fdCallback_ = callback;
    }

private:
    static void OnTimer(uv_timer_t *timer)
    {
        auto &that = GetInstance(timer);
        if (that.timerCallback_) {
            that.timerCallback_();
        }
    }

    static void AsyncCallback(uv_async_t* handle) {
        auto &that = GetInstance(handle);
        auto loop = handle->loop;
        if (that.timerStart_) {
            if (!that.timer_) {
                that.timer_ = new uv_timer_t;
                uv_timer_init(loop, that.timer_);
                that.timer_->data = handle->data;
            }
            uv_timer_start(that.timer_, OnTimer, that.nanoseconds_ / 1e6, 0);
        } else if (that.timerStop_) {
            if (that.timer_) {
                uv_close((uv_handle_t*)that.timer_, [](uv_handle_t* handle) {
                    delete(uv_timer_t*)handle;
                    handle = nullptr;
                });
            }
        }

        if (that.asyncHandleClose_) {
            if (handle) {
                uv_close((uv_handle_t*)handle, [](uv_handle_t* handle){
                    delete (uv_async_t*)handle;
                    handle = nullptr;
                });
            }
        }
    }

    template<typename T>
    static PlatformIoWaiter &GetInstance(T *that)
    {
        return *reinterpret_cast<PlatformIoWaiter *>(that->data);
    }

    void SetAsyncCallbackParam(bool asyncHandleClose, bool timerStart, bool timerStop, int64_t nanoseconds) {
        asyncHandleClose_ = asyncHandleClose;
        timerStart_ = timerStart;
        timerStop_ = timerStop;
        nanoseconds_ = nanoseconds;
    }

    uv_loop_s* loop_ = nullptr;
    uv_async_t* async_ = nullptr;
    uv_timer_t* timer_ = nullptr;
    bool timerStart_ = false;
    bool timerStop_ = false;
    bool asyncHandleClose_ = false;
    int64_t nanoseconds_ = 0;
    TimerCallback timerCallback_;
    FileDescriptorEventCallback fdCallback_;
};

bool PlatformEventRunner::CheckCurrent()
{
    return std::this_thread::get_id() == g_mainThreadId;
}

PlatformEventRunner::PlatformEventRunner(const std::shared_ptr<EventRunner>& runner)
    : EventInnerRunner(runner), ioWaiter_(std::make_shared<PlatformIoWaiter>())
{
    queue_ = std::make_shared<EventQueue>(ioWaiter_);

    // Set current event runner
    currentEventRunner = owner_;
}

PlatformEventRunner::~PlatformEventRunner()
{
    ioWaiter_->StopTimer();
}

void PlatformEventRunner::Run()
{
    InnerEvent::TimePoint wakeUpTime = InnerEvent::TimePoint::max();
    for (auto event = queue_->GetExpiredEvent(wakeUpTime); event; event = queue_->GetExpiredEvent(wakeUpTime)) {
        std::shared_ptr<EventHandler> handler = event->GetOwner();
        // Make sure owner of the event exists.
        if (handler) {
            handler->DistributeEvent(event);
        }
    }
    ioWaiter_->SetTimer(TimePointToTimeOut(wakeUpTime));
}

void PlatformEventRunner::Stop() {}

bool PlatformEventRunner::Init()
{
    threadId_ = std::this_thread::get_id();
    return ioWaiter_->StartTimer(std::bind(&PlatformEventRunner::Run, this));
}
} // namespace AppExecFwk
} // namespace Keels